﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using MonoStrategy.RenderSystem;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization;

namespace MonoStrategy
{
    [ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
    public class Control
    {
        public const int InvalidMouseButton = -1;
        public const int LeftMouseButton = 0;
        public const int MiddleMouseButton = 1;
        public const int RightMouseButton = 2;

        [XmlIgnore]
        private readonly ControlCollection m_Children;
        [XmlIgnore]
        public RootControl RootElement { get; private set; }

        [XmlElement(typeof(Control))]
        [XmlElement(typeof(Image))]
        [XmlElement(typeof(Frame))]
        [XmlElement(typeof(Button))]
        [XmlElement(typeof(ContentPresenter))]
        [XmlElement(typeof(ListBox))]
        [XmlElement(typeof(TabButton))]
        [XmlElement(typeof(RadioButton))]
        [XmlElement(typeof(Label))]
        public List<Control> XMLChildren;

        [XmlAttribute("Template")]
        public String TemplateString { get; set; }

        public virtual void XMLPostProcess(XMLGUILayout inLayout)
        {
            if(inLayout != null)
                RootElement = inLayout.RootElement;

            // append children to templates
            if (!String.IsNullOrEmpty(TemplateString))
            {
                var content = Children;
                var template = inLayout.GetTemplate(TemplateString);
                List<Control> tempChilds;
                List<Control> tempPresenters;

                template.Instantiate(out tempChilds, out tempPresenters);

                foreach (var child in tempChilds)
                {
                    child.RootElement = RootElement;

                    m_Children.Add(child);
                }

                // process children
                if (XMLChildren != null)
                {
                    /*
                     * There might be multiple content presenters in a template and all of them
                     * need to be filled with all the control childs!
                     */
                    foreach (var contentPresenter in tempPresenters)
                    {
                        foreach (var xmlChild in XMLChildren)
                        {
                            var child = (Control)xmlChild;

                            child.XMLPostProcess(inLayout);

                            contentPresenter.Children.Add(child);
                        }
                    }

                    XMLChildren = null;
                }
            }
            else
            {
                // process children
                if (XMLChildren != null)
                {
                    foreach (var xmlChild in XMLChildren)
                    {
                        var child = (Control)xmlChild;

                        child.XMLPostProcess(inLayout);

                        Children.Add(child);
                    }

                    XMLChildren = null;
                }
            }
        }

        [XmlIgnore]
        public ICollection<Control> Children { get { return m_Children; } }
        [XmlIgnore]
        public Control Parent { get; private set; }
        [XmlAttribute]
        public Double Opacity { get; set; }
        [XmlIgnore]
        public GLRenderer Renderer { get { return Program.GUIConfig.Renderer; } }
        [XmlAttribute("DataContext")]
        public String DataContextString { get; set; }
        [XmlAttribute]
        public Int32 Width { get; set; }
        [XmlAttribute]
        public String Id { get; set; }
        [XmlAttribute]
        public Int32 Height { get; set; }
        [XmlAttribute]
        public Int32 Left { get; set; }
        [XmlAttribute]
        public Int32 Top { get; set; }
        [XmlAttribute]
        public Boolean IsVisible { get; set; }
        [XmlIgnore]
        internal static Double HeightScale { get; set; }
        [XmlIgnore]
        internal static Double WidthScale { get; set; }
        [XmlIgnore]
        public Boolean IsMouseOver { get; private set; }
        [XmlIgnore]
        public Boolean IsMouseDown { get; private set; }
        [XmlAttribute]
        public Boolean IsEnabled { get; set; }
        [XmlIgnore]
        public List<int> MouseState { get; private set; }

        public event DNotifyHandler<Control, int, int, int> OnMouseButtonUp;
        public event DNotifyHandler<Control, int, int, int> OnMouseButtonDown;
        public event DNotifyHandler<Control, int, int> OnMouseMove;
        public event DNotifyHandler<Control> OnMouseEnter;
        public event DNotifyHandler<Control> OnMouseLeave;

        public Control()
        {
            MouseState = new List<int>(0);
            m_Children = new ControlCollection(this);

            Opacity = 1;
            IsVisible = true;
            IsEnabled = true;
        }

        public Control(params Control[] inControls) : this()
        {
            foreach (var child in inControls)
            {
                Children.Add(child);
            }
        }

        public Control FindControl(String inId)
        {
            if (Id == inId)
                return this;

            if (m_Children != null)
            {
                foreach (var child in m_Children)
                {
                    var ctrl = child.FindControl(inId);

                    if (ctrl != null)
                        return ctrl;
                }
            }
            else
                Id = null;

            if (XMLChildren != null)
            {
                /*
                 * This method is also being used during load time, so we need to
                 * search XML children if available...
                 */
                foreach (var child in XMLChildren)
                {
                    var ctrl = child.FindControl(inId);

                    if (ctrl != null)
                        return ctrl;
                }
            }

            return null;
        }

        protected void EnumVisibleChildren(Procedure<Control> inEnumHandler)
        {
            foreach (var child in m_Children)
            {
                if (child.IsVisible)
                {
                    /*
                     * If no height or width is specified, the child inherits its size
                     * from parent. This is important for templates or at least makes
                     * them easier.
                     */
                    int widthBackup = child.Width;
                    int heightBackup = child.Height;

                    if (widthBackup == 0)
                        child.Width = Width;
                    if (heightBackup == 0)
                        child.Height = Height;

                    try
                    {
                        inEnumHandler(child);
                    }
                    finally
                    {
                        child.Width = widthBackup;
                        child.Height = heightBackup;
                    }
                }
            }
        }

        internal bool ProcessMouseEventInternal(int inMouseX, int inMouseY, int inButtonDown, int inButtonUp)
        {
            bool result = false;

            EnumVisibleChildren((child) =>
            {
                if (!child.IsEnabled)
                    return;

                int childMouseX = inMouseX - child.Left;
                int childMouseY = inMouseY - child.Top;

                // is within component?
                if ((childMouseX > child.Width) || (childMouseY > child.Height) ||
                     (childMouseX < 0) || (childMouseY < 0))
                {
                    child.ResetMouseEvents();

                    return;
                }

                result = true;

                // notify child
                child.RaiseMouseEvents(childMouseX, childMouseY, inButtonDown, inButtonUp);

                // propagate event to subchilds
                child.ProcessMouseEventInternal(childMouseX, childMouseY, inButtonDown, inButtonUp);
            });

            return result;
        }

        private void ResetMouseEvents()
        {
            if (IsMouseOver)
            {
                IsMouseOver = false;

                DoMouseLeave();

                if (OnMouseLeave != null)
                    OnMouseLeave(this);
            }

            // no button up event will be raised if mouse left control during click...
            MouseState.Clear();
            IsMouseDown = false;

            foreach (var child in Children)
            {
                child.ResetMouseEvents();
            }
        }

        private void RaiseMouseEvents(int inMouseX, int inMouseY, int inButtonDown, int inButtonUp)
        {
            // mouse is guaranteed to be within bounds...

            if (!IsMouseOver)
            {
                IsMouseOver = true;

                DoMouseEnter();

                if (OnMouseEnter != null)
                    OnMouseEnter(this);
            }

            if (inButtonUp != InvalidMouseButton)
            {
                if (MouseState.Contains(inButtonUp))
                {
                    MouseState.Remove(inButtonUp);

                    IsMouseDown = MouseState.Contains(LeftMouseButton);

                    DoMouseButtonUp(inMouseX, inMouseY, inButtonUp);

                    if (OnMouseButtonUp != null)
                        OnMouseButtonUp(this, inMouseX, inMouseY, inButtonUp);
                }
            }

            if (inButtonDown != InvalidMouseButton)
            {
                if (!MouseState.Contains(inButtonDown))
                {
                    MouseState.Add(inButtonDown);

                    IsMouseDown = MouseState.Contains(LeftMouseButton);

                    DoMouseButtonDown(inMouseX, inMouseY, inButtonUp);

                    if (OnMouseButtonDown != null)
                        OnMouseButtonDown(this, inMouseX, inMouseY, inButtonDown);
                }
            }

            DoMouseMove(inMouseX, inMouseY);

            if (OnMouseMove != null)
                OnMouseMove(this, inMouseX, inMouseY);
        }

        protected virtual void DoMouseButtonUp(int inMouseX, int inMouseY, int inButton) { }
        protected virtual void DoMouseButtonDown(int inMouseX, int inMouseY, int inButton) { }
        protected virtual void DoMouseMove(int inMouseX, int inMouseY) { }
        protected virtual void DoMouseEnter() { }
        protected virtual void DoMouseLeave() { }

        protected virtual void Render(int inChainLeft, int inChainTop)
        {
            EnumVisibleChildren((child) => { child.Render(inChainLeft + Left, inChainTop + Top); });
        }

        private class ControlCollection : ICollection<Control>
        {
            [XmlIgnore]
            private readonly List<Control> m_Children = new List<Control>();
            [XmlIgnore]
            public Control This { get; private set; }

            public ControlCollection(Control inThis)
            {
                This = inThis;
            }

            public void Add(Control item)
            {
                if (item.Parent != null)
                    throw new InvalidOperationException("Control is already attached.");

                m_Children.Add(item);
                item.Parent = This;
            }

            public void Clear()
            {
                foreach (var child in m_Children)
                {
                    child.Parent = null;
                }

                m_Children.Clear();
            }

            public bool Contains(Control item)
            {
                return m_Children.Contains(item);
            }

            public void CopyTo(Control[] array, int arrayIndex)
            {
                m_Children.CopyTo(array, arrayIndex);
            }

            [XmlIgnore]
            public int Count
            {
                get { return m_Children.Count; }
            }

            [XmlIgnore]
            public bool IsReadOnly
            {
                get { return false; }
            }

            public bool Remove(Control item)
            {
                int pos = m_Children.IndexOf(item);

                if (pos < 0)
                    return false;

                item.Parent = null;
                m_Children.RemoveAt(pos);

                return true;
            }

            public IEnumerator<Control> GetEnumerator()
            {
                return m_Children.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return m_Children.GetEnumerator();
            }
        }
    }
}
